﻿#include "framelesswindow.h"
#include <QApplication>
#include <QPoint>
#include <QSize>
#include <QDebug>
#include <QScreen>

#ifdef Q_OS_WIN

#include <windows.h>
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <gdiplus.h>

CFramelessWindow::CFramelessWindow(QWidget *parent)
        : QMainWindow(parent),
          m_titlebar(Q_NULLPTR),
          m_borderWidth(5),
          m_bResizeable(true) {
    //    setWindowFlag(Qt::Window,true);
    //    setWindowFlag(Qt::FramelessWindowHint, true);
    //    setWindowFlag(Qt::WindowSystemMenuHint, true);
    //    setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead
    // 似乎要有Qt::WindowMaximizeButtonHint才能双击标题栏最大化和恢复不是最大化
    setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint /*| Qt::WindowMaximizeButtonHint*/ |
                   Qt::WindowSystemMenuHint);

    setResizeable(m_bResizeable);
}

void CFramelessWindow::setResizeable(bool resizeable) {
    bool visible = isVisible();
    m_bResizeable = resizeable;

    //this line will get titlebar/thick frame/Aero back, which is exactly what we want
    //we will get rid of titlebar and thick frame again in nativeEvent() later
    // WS_CAPTION会造成在win7出现原生的最小最大关闭按钮，win10正常
    HWND hwnd = (HWND) this->winId();
    DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
    //::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
    ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_CAPTION | WS_THICKFRAME | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

    //保留一个像素的边框宽度，否则系统不会绘制边框阴影, 需要WS_THICKFRAME才会有阴影
    //we better left 1 piexl width of border untouch, so OS can draw nice shadow around it
//    const MARGINS shadow = {1, 1, 1, 1};
//    DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);

    setVisible(visible);
}

void CFramelessWindow::setResizeableAreaWidth(int width) {
    if (0 > width) width = 0;
    m_borderWidth = width;
}

void CFramelessWindow::setTitleBar(QWidget *titlebar) {
    m_titlebar = titlebar;
    if (!titlebar) return;
    connect(titlebar, SIGNAL(destroyed(QObject * )), this, SLOT(onTitleBarDestroyed()));
}

void CFramelessWindow::onTitleBarDestroyed() {
    if (m_titlebar == QObject::sender()) {
        m_titlebar = Q_NULLPTR;
    }
}

void CFramelessWindow::addIgnoreWidget(QWidget *widget) {
    if (!widget) return;
    if (m_whiteList.contains(widget)) return;
    m_whiteList.append(widget);
}

bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) {
    // In your reimplementation of this function,
    // if you want to stop the event being handled by Qt, return true and set result.
    // The result parameter has meaning only on Windows.
    // If you return false, this native event is passed back to Qt, which translates the event into a Qt event and sends it to the widget.
    //Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
    MSG* msg = *reinterpret_cast<MSG**>(message);
#else
    MSG *msg = reinterpret_cast<MSG *>(message);
#endif

    switch (msg->message) {
        case WM_NCCALCSIZE: {
            LPRECT pRect = NULL;

            if (msg->wParam == TRUE) {
                LPNCCALCSIZE_PARAMS pParam = (LPNCCALCSIZE_PARAMS) msg->lParam;
                pRect = &pParam->rgrc[0];
            } else {
                pRect = (LPRECT) msg->lParam;
            }

            if (::IsZoomed(msg->hwnd)) {    // 最大化时，计算当前显示器最适合宽高度
#if 0
                MONITORINFO oMonitor = {};
                oMonitor.cbSize = sizeof(oMonitor);
                ::GetMonitorInfo(::MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
                QRect rcWork(oMonitor.rcWork.left, oMonitor.rcWork.top,
                             oMonitor.rcWork.right - oMonitor.rcWork.left, oMonitor.rcWork.bottom - oMonitor.rcWork.top);
                QRect rcMonitor(oMonitor.rcMonitor.left, oMonitor.rcMonitor.top,
                                oMonitor.rcMonitor.right - oMonitor.rcMonitor.left, oMonitor.rcMonitor.bottom - oMonitor.rcMonitor.top);
                rcWork.translate(-oMonitor.rcMonitor.left, -oMonitor.rcMonitor.top);
#else
                QRect rcWork = QGuiApplication::primaryScreen()->virtualGeometry();
                QRect rcMonitor = QGuiApplication::primaryScreen()->geometry();
                rcWork.translate(-rcMonitor.left(), -rcMonitor.top());
#endif

                pRect->right = pRect->left + rcWork.width();
                pRect->bottom = pRect->top + rcWork.height();
                *result = WVR_REDRAW;
                return true;
            }
            //*result = 0;
            *result = WVR_REDRAW;
            return true;
        }
            break;
        case WM_NCHITTEST: {
            *result = 0;
            //qDebug() << "m_borderWidth:" << m_borderWidth << ",m_bResizeable:" << m_bResizeable;
            const LONG border_width = m_borderWidth;
            RECT winrect;
            GetWindowRect(HWND(winId()), &winrect);

            long x = GET_X_LPARAM(msg->lParam);
            long y = GET_Y_LPARAM(msg->lParam);
            if (m_bResizeable && border_width > 0) {
                bool resizeWidth = minimumWidth() != maximumWidth() && !::IsZoomed(msg->hwnd);
                bool resizeHeight = minimumHeight() != maximumHeight() && !::IsZoomed(msg->hwnd);

                if (resizeWidth) {
                    //left border
                    if (x >= winrect.left && x < winrect.left + border_width) {
                        *result = HTLEFT;
                    }
                    //right border
                    if (x < winrect.right && x >= winrect.right - border_width) {
                        *result = HTRIGHT;
                    }
                }
                if (resizeHeight) {
                    //bottom border
                    if (y < winrect.bottom && y >= winrect.bottom - border_width) {
                        *result = HTBOTTOM;
                    }
                    //top border
                    if (y >= winrect.top && y < winrect.top + border_width) {
                        *result = HTTOP;
                    }
                }
                if (resizeWidth && resizeHeight) {
                    //bottom left corner
                    if (x >= winrect.left && x < winrect.left + border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width) {
                        *result = HTBOTTOMLEFT;
                    }
                    //bottom right corner
                    if (x < winrect.right && x >= winrect.right - border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width) {
                        *result = HTBOTTOMRIGHT;
                    }
                    //top left corner
                    if (x >= winrect.left && x < winrect.left + border_width &&
                        y >= winrect.top && y < winrect.top + border_width) {
                        *result = HTTOPLEFT;
                    }
                    //top right corner
                    if (x < winrect.right && x >= winrect.right - border_width &&
                        y >= winrect.top && y < winrect.top + border_width) {
                        *result = HTTOPRIGHT;
                    }
                }
            }
            if (0 != *result) {
                //qDebug() << "result:" << *result;
                return true;
            }

            //*result still equals 0, that means the cursor locate OUTSIDE the frame area
            //but it may locate in titlebar area
            if (m_titlebar) {
                //support highdpi
                double dpr = this->devicePixelRatioF();
                //qDebug() << "devicePixelRatioF:" << devicePixelRatioF();
                QPoint pos = m_titlebar->mapFromGlobal(QPoint(x / dpr, y / dpr));
                if (m_titlebar->rect().contains(pos)) {
                    QWidget *child = m_titlebar->childAt(pos);
                    if (nullptr == child // 直接在标题栏
                        || m_whiteList.contains(child) // 在标题栏白名单上
                            ) {
                        *result = HTCAPTION;
                        //qDebug() << "result:" << *result;
                        return true;
                    }
                }
            }
            *result = HTCLIENT;
            return true;
            //return false;
        }
            break;
        case WM_GETMINMAXINFO: {
            LPMINMAXINFO lpMMI = (LPMINMAXINFO) msg->lParam;
#if 0
            MONITORINFO oMonitor = {};
            oMonitor.cbSize = sizeof(oMonitor);
            ::GetMonitorInfo(::MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
            QRect rcWork(oMonitor.rcWork.left, oMonitor.rcWork.top,
                         oMonitor.rcWork.right - oMonitor.rcWork.left, oMonitor.rcWork.bottom - oMonitor.rcWork.top);
            QRect rcMonitor(oMonitor.rcMonitor.left, oMonitor.rcMonitor.top,
                            oMonitor.rcMonitor.right - oMonitor.rcMonitor.left, oMonitor.rcMonitor.bottom - oMonitor.rcMonitor.top);
            rcWork.translate(-oMonitor.rcMonitor.left, -oMonitor.rcMonitor.top);
            qDebug() << "rcWork:" << rcWork << ", rcMonitor:" << rcMonitor;
#else
            QRect rcWork = QGuiApplication::primaryScreen()->availableGeometry();
            QRect rcMonitor = QGuiApplication::primaryScreen()->geometry();
            rcWork.translate(-rcMonitor.left(), -rcMonitor.top());  // 屏幕坐标一般都是0,但是多屏幕时启动屏幕的左上角不是0
            //qDebug() << "availableGeometry:" << pDesktop->availableGeometry(this) << ",screenGeometry:" << pDesktop->screenGeometry(this);
            //qDebug() << "rcWork:" << rcWork << ", rcMonitor:" << rcMonitor;
#endif
            // 计算最大化时，正确的原点坐标
            // 设置窗口最大化时的宽度、高度
            lpMMI->ptMaxSize.x = rcWork.width();
            lpMMI->ptMaxSize.y = rcWork.height();
            // 设置窗口最大化时x坐标、y坐标，似乎要填入0值
            lpMMI->ptMaxPosition.x = rcWork.left();
            lpMMI->ptMaxPosition.y = rcWork.top();
            // 设置窗口最大宽度、高度
            lpMMI->ptMaxTrackSize.x = rcWork.width();
            lpMMI->ptMaxTrackSize.y = rcWork.height();
            // 设置窗口最小宽度、高度
            lpMMI->ptMinTrackSize.x = this->minimumWidth();
            lpMMI->ptMinTrackSize.y = this->minimumHeight();

            return true;
        }
            break;
#if 0
            case WM_SIZE:
            {
                SIZE szRoundCorner = {3, 3};    // 圆角
#if defined(WIN32) && !defined(UNDER_CE)
                if( !::IsIconic(msg->hwnd) && (szRoundCorner.cx != 0 || szRoundCorner.cy != 0) )
                {
                    RECT rcWnd;
                    ::GetWindowRect(msg->hwnd, &rcWnd);

                    QRect rcWndQt(rcWnd.left, rcWnd.top, rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top);
                    rcWndQt.translate(-rcWnd.left, -rcWnd.top);
                    rcWnd.left = rcWndQt.left();
                    rcWnd.top = rcWndQt.top();
                    rcWnd.right = rcWndQt.right();
                    rcWnd.bottom = rcWndQt.bottom();

                    rcWnd.right += 2;
                    rcWnd.bottom += 2;
                    HRGN hRgn = ::CreateRoundRectRgn(rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy);
                    ::SetWindowRgn(msg->hwnd, hRgn, TRUE);
                    ::DeleteObject(hRgn);
                }
#endif
                return false;
            }break;
#endif
        default:
            return QMainWindow::nativeEvent(eventType, message, reinterpret_cast<qintptr *>(result));
    }

    return QMainWindow::nativeEvent(eventType, message, reinterpret_cast<qintptr *>(result));
}

#endif //Q_OS_WIN
